package cloudspace.vm.io.console;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cloudspace.vm.VM;


/**
 * This class holds all of the registered streams and creates a master stream
 * out of them. Individual streams are registered and the Console then
 * aggregates their stream lines into one sorted output.
 */
public class Console
{

    private class LoggedInfoStream
    {
        long currentLineNumber;

        InfoStream iStream;


        public LoggedInfoStream( InfoStream infoStream )
        {
            iStream = infoStream;
            currentLineNumber = iStream.getLineCacheStart();
        }


        public InfoStream getInfoStream()
        {
            return iStream;
        }


        public List<InfoStreamLine> getNewLines()
        {
            synchronized ( iStream )
            {
                try
                {
                    List<InfoStreamLine> lines = iStream.getLines( currentLineNumber );
                    currentLineNumber = lines.get( lines.size() - 1 )
                        .getLineNumber();
                    return lines;
                }
                catch ( FlushedException e )
                {
                    currentLineNumber = iStream.getLineCacheStart();
                    return getNewLines();
                }

            }
        }


        public boolean isDirty()
        {
            synchronized ( iStream )
            {
                return iStream.getLineNumber() > currentLineNumber;
            }
        }
    }


    /**
     * This class compares two different InfoStreamLines. First looking at the
     * time stamps then looking at line numbers. However, line numbers are only
     * used when the timestamps are the same and both info stream lines come
     * from the same streamID.
     */
    public class InfoStreamLineComparator implements Comparator<InfoStreamLine>
    {

        /*
         * (non-Javadoc)
         * 
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare( InfoStreamLine o1, InfoStreamLine o2 )
        {
            if ( o1.getTimestamp() < o2.getTimestamp() )
            {
                return -1;
            }
            else if ( o1.getTimestamp() > o2.getTimestamp() )
            {
                return 1;
            }
            else if ( o1.getStreamID().equals( o2.getStreamID() ) )
            {
                if ( o1.getLineNumber() > o2.getLineNumber() )
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            return 0;
        }

    }

    /** The id stream map. */
    private Map<String, LoggedInfoStream> idStreamMap;


    /**
     * Instantiates a new stream provider.
     */
    public Console()
    {
        idStreamMap = new HashMap<String, LoggedInfoStream>();
        init();
    }


    /**
     * Create a console with both a standard out and error stream. If you want
     * standard out and error to be registered to this console, you MUST use
     * this method.
     * 
     * @param stdOut
     * @param error
     */
    public Console( InfoStream stdOut, InfoStream error )
    {
        this();
        registerInfoStream( stdOut );
        registerInfoStream( error );
        init();
    }


    public void init()
    {
        registerInfoStream( VM.currentVM().getLog() );

    }


    /**
     * Gets all of the new lines stored in the console that need to be printed
     * out.
     * 
     * @return a set of new lines in descending order.
     * 
     * @throws Exception
     *             This throws an exception if there is any problem accessing
     *             the new infostreams. This is important for infostreams based
     *             off of files and such.
     */
    public List<InfoStreamLine> getNewOrderedLines()
    {
        List<InfoStreamLine> orderedNewLines = new LinkedList<InfoStreamLine>();
        Set<String> mapKeys = idStreamMap.keySet();
        Iterator<String> keyIter = mapKeys.iterator();
        while ( keyIter.hasNext() )
        {
            LoggedInfoStream curStream = idStreamMap.get( keyIter.next() );
            if ( curStream.isDirty() )
                orderedNewLines.addAll( curStream.getNewLines() );
            // orderedNewLines.addAll(tempNewLines);
        }
        Collections.sort( orderedNewLines, new InfoStreamLineComparator() );
        return orderedNewLines;
    }


    /**
     * Checks if any of the streams in this console are dirty.
     * 
     * @return true, if any of the streams are dirty
     */
    public boolean isDirty()
    {
        Set<String> mapKeys = idStreamMap.keySet();
        Iterator<String> keyIter = mapKeys.iterator();
        while ( keyIter.hasNext() )
        {
            LoggedInfoStream curStream = idStreamMap.get( keyIter.next() );
            if ( curStream.isDirty() )
            {
                return true;
            }
        }
        return false;
    }


    /**
     * Get all of the registered stream ids.
     * 
     * @return all of the stream ids registered to this console.
     */
    public Iterator<String> getStreamIDs()
    {
        return idStreamMap.keySet().iterator();
    }


    /**
     * Register an info stream to the current console. It must not have a stream
     * id matching one of the protected ids such as system out and system error.
     * 
     * @param iStream
     *            The info stream to log
     * @param id
     *            The id for the stream to be logged
     */
    public void registerInfoStream( InfoStream iStream )
    {
        idStreamMap.put( iStream.getStreamID(), new LoggedInfoStream( iStream ) );

    }


    /**
     * Gets the system out stream. This is one of the protected streams of the
     * console.
     * 
     * @return the system out stream
     */
    public InfoStream getSystemOutStream()
    {
        return idStreamMap.get( VM.standardOutTag ).getInfoStream();
    }


    /**
     * Gets the system error stream. this is one of the protected streams of the
     * console.
     * 
     * @return the system error stream
     */
    public InfoStream getSystemErrorStream()
    {
        return idStreamMap.get( VM.standardErrorTag ).getInfoStream();
    }


    public InfoStream getSystemLogStream()
    {
        return idStreamMap.get( VM.logTag ).getInfoStream();
    }

}
